14장 전역 변수의 문제점
14.1 변수의 생명 주기
14.1.1 지역 변수의 생명 주기
function foo() {
var x = "local";
console.log(x); // local
return x;
}
foo();
console.log(x); // ReferenceError: x is not defined
4.4장에서 나왔던 “변수 선언은 선언문이 어디에 있든 상관없이 가장 먼저 실행된다.” 는 설명은 사실 전역 변수에 국한된 설명이다. 함수 스코프 내부의 지역번수는 Execution Context의 Creation Phase에 초기화된다.
언제나 그랬듯이 Execution Context는 나중에 알아보자 ^~^
위의 예시에 나와있는 foo()
함수가 호출될 때, 변수 x
가 가장 먼저 undifined
로 초기화된 후, 함수 내부가 순차적으로 실행되고 foo
가 return
할 때 x
의 생명 주기도 같이 종료된다.
즉 지역번수의 생명 주기는 변수가 선언된 Lexcial Scope의 생명 주기와 일치한다. 함수가 종료되어도 함수 내부에서 생성한 객체에 대한 참조가 외부에 남아있을 경우 그 객체는 사라지지 않기 떄문이다.
var x = "global";
function foo() {
console.log(x); // 1. What is the value of x?
var x = "local";
}
foo();
console.log(x); // 2. What is the value of x?
위와 같은 상황에서 첫 번째 출력은 undifined
, 두 번째 출력은 'global'
이 될 것이다. 지역 변수는 전역 변수보다 우선순위를 가지고, 변수 호이스팅은 스코프 단위로 발생하기 때문에 첫 번째 console.log(x)
에서 참조하는 x
는 지역번수이고 undifined
로 초기화되어있는 상태이다.
14.1.2 전역 변수의 생명 주기
전역 변수는 앞서 설명했던 호이스팅이 스크립트 전체의 영역에서 발생한다. var
로 생성한 전역 변수는 전역 객체의 프로퍼티가 되기 때문에 전역 객체의 생명 주기와 동일한 생명 주기를 가진다.
ES11에서부터 globalThis
라는 객체로 전역 객체의 식별자가 통일되었으나 이전 버전의 js로 작성된 파일의 경우에는 polyfill이 필요하다.
14.2 전역 변수의 문제점
- 암묵적 결합: 전역 번수는 코드의 모든 곳에서 참조하고 수정할 수 있기 때문에 코드의 가독성이 나빠지고 예기치 못한 상황에서 값이 변경되는 결과를 초래한다.
- 긴 생명 주기: 긴 생명 주기를 가지기 때문에 자원또한 지역 변수보다 많이 소모한다.
- 스코프 체인 상에서 가장 긴 거리: 스코프 체이닝을 통해 식별자를 찾는 과정에서 가장 마지막에 검색되기 때문에 검색 속도가 느리다.
- 네임스페이스 오염: 여러 파일이 하나의 전역 스코프를 공유하기 때문에 다른 파일에서 서로 같은 이름으로 전역 변수를 선언할 경우 예상치 못한 결과가 발생할 수 있다.
14.3 전역 변수의 사용을 억제하는 방법
지역 변수를 사용할 수 있는 상황이면 전역 변수 사용을 지양하도록 하자. 아래는 전역 변수 사용을 억제할 수 있는 방법들이다.
14.3.1 즉시 실행 함수
모든 코드를 IIFE로 감싸 즉시 실행 함수의 지역 변수로 만드는 방법이다.
(function () {
var foo = 10;
// ...
})();
console.log(foo); // ReferenceError: foo is not defined
이런 코드가 진짜로 사용되나요? 전 한 번도 못 봤어요..
14.3.2 네임스페이스 객체
전역 네임스페이스 객체를 생성하고 해당 객체에 사용하고 싶은 변수를 프로퍼티로 추가하는 방법이다.
var MYAPP = {};
MYAPP.name = "Lee";
14.3.3 모듈 패턴
나중에 설명할 클로저를 이용해 관련 있는 변수와 함수들을 IIFE로 감싸 모듈을 만드는 방법이다.
var Counter = function () {
var num = 0;
return {
increase() {
return ++num;
},
decrease() {
return --num;
},
};
};
위의 예시에서 num
은 반환값에 포함되어 있지 않기 때문에 직접 접근할 수 없지만 Counter.increase()
. Counter.decrease()
메소드를 이용해 num
의 값을 수정할 수 있다.
위의 코드에서 Counter
를 통해 increase()
, decrease()
에 접근할 수 있기 때문에 해당 함수들의 Outer Lexical Environment에 포함되어 있는 var
라는 변수가 죽지 않고 유지된다.
14.3.4 ES6 모듈
import
를 써서 ES6 모듈의 형태로 코드를 관리하면 각 파일 자체에 독자적인 스코프가 생성되며 var
로 변수를 선언하더라도 전역 변수도 아니고 전역 객체의 프로퍼티가 되지도 않는다. 책에서는 ES6의 자체 모듈보다 Webpack과 같은 모듈 번들러를 사용할 것을 권장한다.
현재 사용되는 대부분의 브라우저에서 자체적으로 ES6 문법을 지원하기 때문에 사용해도 큰 문제는 없다고 생각한다. 만약 Next.js 같은 프레임워크를 사용하게 된다면 번들러를 자체적으로 포함하고 있다. ![[Can I use es6.png]]